#include "LeGraphicsStyle.h"

#include <QDebug>
#include <QPainter>
#include <QVector>

LeGraphicsStyle::LeGraphicsStyle(QObject *parent):
    QObject(parent)
{

}

LeGraphicsStyle::~LeGraphicsStyle()
{

}

void LeGraphicsStyle::drawCircle(const GraphicsStyleOption *option, QPainter *painter, const QPointF &center, qreal diameter)
{
    const qreal diameterPix = painter->worldTransform().map(QLineF(0, 0, diameter, 0)).length();

    if (diameterPix < 0.01)
        return;


    QBrush fillBrush = fillFeatureBrush(option, painter);
    QPen fillPen = fillFeaturePen(option, fillBrush);

    if (diameterPix < 2.0)
    {
        if (fillPen.style() == Qt::NoPen)
            painter->setPen(QPen(fillBrush.color(), 0.0));
        else
            painter->setPen(fillPen);
        painter->drawPoint(center);
    }
    else if (diameterPix < 5.0)
    {
        const QRectF ellipseRect(center.x() - diameter/2.0, center.y() - diameter/2.0,
                                 diameter, diameter);
        painter->setBrush(fillBrush);
        painter->setPen(fillPen);
        painter->drawRect(ellipseRect);
    }
    else
    {
        const QRectF ellipseRect(center.x() - diameter/2.0, center.y() - diameter/2.0,
                                 diameter, diameter);
        painter->setClipRect(option->exposedRect.adjusted(-1, -1, 1, 1));
        painter->setBrush(fillBrush);
        painter->setPen(fillPen);
        painter->drawEllipse(ellipseRect);
    }
}

void LeGraphicsStyle::drawRect(const GraphicsStyleOption *option, QPainter *painter, const QRectF &rect)
{
    const QRectF rectPix = painter->worldTransform().map(rect).boundingRect();
    const qreal widthPix = rectPix.width();
    const qreal heightPix = rectPix.height();

    if (widthPix < 0.01 && heightPix < 0.01)
        return;

    QBrush fillBrush = fillFeatureBrush(option, painter);
    QPen fillPen = fillFeaturePen(option, fillBrush);

    if (widthPix < 2.0 && heightPix < 2.0)
    {
        if (fillPen.style() == Qt::NoPen)
            painter->setPen(QPen(fillBrush.color(), 0.0));
        else
            painter->setPen(fillPen);
        painter->drawPoint(rect.center());
    }
    // HeightPix/WidthPix might not map to HeightScene/WidthScene (rotation)
    else if (heightPix < 2.0 || widthPix < 2.0)
    {
        if (fillPen.style() == Qt::NoPen)
            painter->setPen(QPen(fillBrush.color(), 0.0));
        else
            painter->setPen(fillPen);
        painter->drawLine(rect.topLeft(), rect.bottomRight());
    }
    else
    {
        painter->setClipRect(option->exposedRect.adjusted(-1, -1, 1, 1));
        painter->setBrush(fillBrush);
        painter->setPen(fillPen);
        painter->drawRect(rect);
    }
}

void LeGraphicsStyle::fillPath(const GraphicsStyleOption *option, QPainter *painter, const QPainterPath &path)
{
    const QRectF rectPix = painter->worldTransform().map(path.boundingRect()).boundingRect();
    const qreal widthPix = rectPix.width();
    const qreal heightPix = rectPix.height();

    if (widthPix < 0.01 && heightPix < 0.01)
        return;

    QBrush fillBrush = fillFeatureBrush(option, painter);
    QPen fillPen = fillFeaturePen(option, fillBrush);

    if (widthPix < 2.0 && heightPix < 2.0)
    {
        if (fillPen.style() == Qt::NoPen)
            painter->setPen(QPen(fillBrush.color(), 0.0));
        else
            painter->setPen(fillPen);
        painter->drawPoint(path.boundingRect().center());
    }
    else if (widthPix < 5.0 && heightPix < 5.0)
    {
        painter->setBrush(fillBrush);
        painter->setPen(fillPen);
        painter->drawRect(path.boundingRect());
    }
    else
    {
        painter->setClipRect(option->exposedRect.adjusted(-1, -1, 1, 1));
        painter->setBrush(fillBrush);
        painter->setPen(fillPen);
        painter->drawPath(path);
    }
}

void LeGraphicsStyle::drawOrigin(const GraphicsStyleOption *option, QPainter *painter, const QPointF &pos)
{
    const QColor color = option->palette.color(LeGraphicsPalette::Origin);
    static const int screenSize = 20;
    qreal sceneSize = painter->combinedTransform().inverted().map(QLineF(0, 0, 0, screenSize)).length();
    painter->setPen(QPen(color, 0));
    painter->drawEllipse(pos, sceneSize/2.0, sceneSize/2.0);
    painter->drawLine(QLineF(pos.x(), pos.y()-sceneSize, pos.x(), pos.y() + sceneSize));
    painter->drawLine(QLineF(pos.x()-sceneSize, pos.y(), pos.x()+sceneSize, pos.y()));
}

void LeGraphicsStyle::drawCursor(const GraphicsStyleOption *option, QPainter *painter, const QPointF &pos)
{
    const QColor color = option->palette.color(LeGraphicsPalette::Origin);
    static const int screenSize = 10;
    qreal sceneSize = painter->combinedTransform().inverted().map(QLineF(0, 0, 0, screenSize)).length();
    painter->setPen(QPen(color, 0));
    painter->drawEllipse(pos, sceneSize/2.0, sceneSize/2.0);
    painter->drawLine(QLineF(pos.x(), pos.y()-sceneSize, pos.x(), pos.y() + sceneSize));
    painter->drawLine(QLineF(pos.x()-sceneSize, pos.y(), pos.x()+sceneSize, pos.y()));
}

void LeGraphicsStyle::drawMajorGrid(const GraphicsStyleOption *option, QPainter *painter,
                                    const QVector<qreal> &xValues, const QVector<qreal> &yValues,
                                    const QPointF &topLeft, const QPointF &bottomRight)
{
    const QColor color = option->palette.color(LeGraphicsPalette::Grid);
    painter->setPen(QPen(color, 0, Qt::SolidLine));

    QVector<QLineF> lines(xValues.count() + yValues.count());
    int j = 0;
    for (const qreal &value: xValues)
        lines[j++].setLine(value, topLeft.y(), value, bottomRight.y());
    for (const qreal &value: yValues)
        lines[j++].setLine(topLeft.x(), value, bottomRight.x(), value);
    painter->drawLines(lines);
}

void LeGraphicsStyle::drawMinorGrid(const GraphicsStyleOption *option, QPainter *painter,
                                    const QVector<qreal> &xValues, const QVector<qreal> &yValues,
                                    const QPointF &topLeft, const QPointF &bottomRight)
{
    const QColor color = option->palette.color(LeGraphicsPalette::Grid);
    painter->setPen(QPen(color, 0, Qt::DotLine));

    QVector<QLineF> lines(xValues.count() + yValues.count());
    int j = 0;
    for (const qreal &value: xValues)
        lines[j++].setLine(value, topLeft.y(), value, bottomRight.y());
    for (const qreal &value: yValues)
        lines[j++].setLine(topLeft.x(), value, bottomRight.x(), value);
    painter->drawLines(lines);
}

static const int __LeGraphicsStyle__HandleSize = 11;
QRectF LeGraphicsStyle::handleBoundingRect(LeGraphicsHandleRole role) const
{
    Q_UNUSED(role)
    return QRectF(-8, -8, 16, 16);
}

// Handles typically ignore transformation, so we paint them accoring to Qt Coordinate system
void LeGraphicsStyle::drawHandle(const GraphicsStyleOption *option, QPainter *painter,
                                 LeGraphicsHandleRole role, qreal rotation)
{
    if (option->state.testFlag(LeGraphicsStyle::Highlighted))
        painter->setBrush(option->palette.color(LeGraphicsPalette::Handle).lighter());
    else
        painter->setBrush(option->palette.color(LeGraphicsPalette::Handle).darker(125));
    painter->setPen(Qt::NoPen);

    const qreal halfSize = __LeGraphicsStyle__HandleSize/2.0;
    QPointF points[4];
    points[0].rx() = - halfSize;
    points[0].ry() = - halfSize;
    points[1].rx() = + halfSize;
    points[1].ry() = - halfSize;
    points[2].rx() = - halfSize;
    points[2].ry() = + halfSize;
    points[3].rx() = + halfSize;
    points[3].ry() = + halfSize;
    switch (role)
    {
        case TopHandleRole:
        case BottomHandleRole:
            painter->rotate(rotation);
            break;
        case LeftHandleRole:
        case RightHandleRole:
            painter->rotate(rotation+90);
            break;
        case TopLeftHandleRole:
        case BottomRightHandleRole:
            painter->rotate(rotation-45);
            break;
        case TopRightHandleRole:
        case BottomLeftHandleRole:
            painter->rotate(rotation+45);
            break;
        case CenterHandleRole:
            painter->rotate(rotation);
            points[0].rx() = - halfSize;
            points[0].ry() = + halfSize;
            points[1].rx() = + halfSize;
            points[1].ry() = + halfSize;
            points[2].rx() = + halfSize;
            points[2].ry() = - halfSize;
            points[3].rx() = - halfSize;
            points[3].ry() = - halfSize;

            break;
    }
    painter->drawPolygon(points, 4, Qt::OddEvenFill);

}

void LeGraphicsStyle::strokePath(const GraphicsStyleOption *option, QPainter *painter,
                                 const QPainterPath &path, const QPainterPath &shape, qreal width)
{
    const qreal widthPix = painter->worldTransform().map(QLineF(0, 0, width, 0)).length();

    if (!qFuzzyIsNull(width) && widthPix < 0.01)
        return;

    QBrush fillBrush = fillFeatureBrush(option, painter);
    QPen fillPen = fillFeaturePen(option, fillBrush);

    if (widthPix < 2.0 )
    {
        if (fillPen.style() == Qt::NoPen)
            painter->setPen(QPen(fillBrush.color(), 0.0));
        else
            painter->setPen(fillPen);
        painter->setBrush(Qt::NoBrush);
        painter->drawPath(path);
    }
    else
    {
        painter->setClipRect(option->exposedRect.adjusted(-1, -1, 1, 1));
        if (fillPen.style() == Qt::NoPen)
        {
            painter->strokePath(path, QPen(fillBrush.color(), width,
                                           Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
        }
        else
        {
            painter->setBrush(fillBrush);
            painter->setPen(fillPen);
            painter->drawPath(shape);
        }
    }
}

void LeGraphicsStyle::drawRubberBand(const GraphicsStyleOption *option, QPainter *painter, const QRectF &rect)
{
    QColor color = option->palette.color(LeGraphicsPalette::RubberBand);
    painter->setClipRect(option->exposedRect.adjusted(-1, -1, 1, 1));
    painter->setPen(QPen(color, 0.0));
    color.setAlphaF(0.25);
    painter->setBrush(color);
    painter->drawRect(rect);
}

void LeGraphicsStyle::drawZoomInBox(const GraphicsStyleOption *option, QPainter *painter, const QRectF &rect)
{
    drawRubberBand(option, painter, rect);

    static const int gap = 4;
    static const int length = 4;

    const QTransform toWorld = painter->worldTransform();
    const QRectF viewRect = toWorld.mapRect(rect).adjusted(gap, gap, -gap, -gap);

    if ((viewRect.width() >= length) &&
            (viewRect.height() >= length))
    {
        const QTransform fromWorld = toWorld.inverted();
        QPointF p1 = fromWorld.map(viewRect.topLeft());
        QPointF p2 = fromWorld.map(QPointF(viewRect.left() + length, viewRect.top()));
        QPointF p3 = fromWorld.map(QPointF(viewRect.left(), viewRect.top() + length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
        p1 = fromWorld.map(viewRect.topRight());
        p2 = fromWorld.map(QPointF(viewRect.right() - length, viewRect.top()));
        p3 = fromWorld.map(QPointF(viewRect.right(), viewRect.top() + length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
        p1 = fromWorld.map(viewRect.bottomRight());
        p2 = fromWorld.map(QPointF(viewRect.right() - length, viewRect.bottom()));
        p3 = fromWorld.map(QPointF(viewRect.right(), viewRect.bottom() - length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
        p1 = fromWorld.map(viewRect.bottomLeft());
        p2 = fromWorld.map(QPointF(viewRect.left() + length, viewRect.bottom()));
        p3 = fromWorld.map(QPointF(viewRect.left(), viewRect.bottom() - length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
    }
}

void LeGraphicsStyle::drawZoomOutBox(const GraphicsStyleOption *option, QPainter *painter, const QRectF &rect)
{
    drawRubberBand(option, painter, rect);

    static const int gap = 4;
    static const int length = 4;

    const QTransform toWorld = painter->worldTransform();
    const QRectF viewRect = toWorld.mapRect(rect).adjusted(gap + length, gap + length,
                                                           -(gap + length), -(gap + length));

    if ((viewRect.width() >= length) &&
            (viewRect.height() >= length))
    {
        const QTransform fromWorld = toWorld.inverted();
        QPointF p1 = fromWorld.map(viewRect.topLeft());
        QPointF p2 = fromWorld.map(QPointF(viewRect.left() - length, viewRect.top()));
        QPointF p3 = fromWorld.map(QPointF(viewRect.left(), viewRect.top() - length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
        p1 = fromWorld.map(viewRect.topRight());
        p2 = fromWorld.map(QPointF(viewRect.right() + length, viewRect.top()));
        p3 = fromWorld.map(QPointF(viewRect.right(), viewRect.top() - length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
        p1 = fromWorld.map(viewRect.bottomRight());
        p2 = fromWorld.map(QPointF(viewRect.right() + length, viewRect.bottom()));
        p3 = fromWorld.map(QPointF(viewRect.right(), viewRect.bottom() + length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
        p1 = fromWorld.map(viewRect.bottomLeft());
        p2 = fromWorld.map(QPointF(viewRect.left() - length, viewRect.bottom()));
        p3 = fromWorld.map(QPointF(viewRect.left(), viewRect.bottom() + length));
        painter->drawLine(p1, p2);
        painter->drawLine(p1, p3);
    }

}

QString LeGraphicsStyle::formatFeatureMetaData(const QMap<QString, QString> metadata)
{
    QStringList lines;
    for (const QString &key: metadata.keys())
        lines.append(QString("%1: <i>%2</i>").arg(key).arg(metadata.value(key)));
    return lines.join("<br/>");
}

QColor LeGraphicsStyle::alphaBlend(const QColor &source, const QColor &dest, qreal alpha)
{
    return QColor::fromRgbF(source.redF() * alpha + dest.redF() * (1.0 - alpha),
                            source.greenF() * alpha + dest.greenF() * (1.0 - alpha),
                            source.blueF() * alpha + dest.blueF() * (1.0 - alpha),
                            1.0);
}

QPen LeGraphicsStyle::fillFeaturePen(const GraphicsStyleOption *option, const QBrush &brush)
{
    if (option->state.testFlag(LeGraphicsStyle::Highlighted))
        return QPen(option->palette.color(LeGraphicsPalette::Highlight),
                    0.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);

    if (brush.style() != Qt::SolidPattern)
    {
        if (option->state.testFlag(LeGraphicsStyle::Selected))
            return QPen(option->palette.color(LeGraphicsPalette::Selection),
                        0.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
        return QPen(option->palette.color(LeGraphicsPalette::Feature),
                    0.0, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
    }

    return QPen(option->palette.color(LeGraphicsPalette::Feature), 0.0, Qt::NoPen);
}

QBrush LeGraphicsStyle::fillFeatureBrush(const GraphicsStyleOption *option, const QPainter *painter)
{
    QBrush brush = option->palette.brush(LeGraphicsPalette::Feature);
    brush.setTransform(painter->worldTransform().inverted());

    if (option->state.testFlag(LeGraphicsStyle::MarkedBad))
        brush.setColor(alphaBlend(option->palette.color(LeGraphicsPalette::BadMark),
                                  brush.color(), 0.8));
    else if (option->state.testFlag(LeGraphicsStyle::MarkedGood))
        brush.setColor(alphaBlend(option->palette.color(LeGraphicsPalette::GoodMark),
                                  brush.color(), 0.8));
    else if (option->state.testFlag(LeGraphicsStyle::Highlighted) &&
             option->state.testFlag(LeGraphicsStyle::Selected))
        brush.setColor(alphaBlend(option->palette.color(LeGraphicsPalette::Selection),
                                  brush.color(), 0.4));
    else if (option->state.testFlag(LeGraphicsStyle::Highlighted))
        brush.setColor(alphaBlend(option->palette.color(LeGraphicsPalette::Highlight),
                                  brush.color(), 0.3));
    else if (option->state.testFlag(LeGraphicsStyle::Selected))
        brush.setColor(alphaBlend(option->palette.color(LeGraphicsPalette::Selection),
                                  brush.color(), 0.5));
    return brush;
}
